/*
 * Neutron3529's Unity Game Plugin
 * Copyright (C) 2022-2023 Neutron3529
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * This program is a part of other mod, which could not be used directly
 * all of the bash script would append this file after the main .cs file
 * if you want to compile program manually, you should append it by yourself.
 */

using System;
using System.Reflection;
using System.Collections.Generic;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using BepInEx.Configuration;
using HarmonyLib;

namespace Neutron3529;
public enum Enable {
    lt,
    le,
    eq,
    ge,
    gt,
    always,
    never
}
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct | System.AttributeTargets.Field, AllowMultiple = false)]
public class Desc : System.Attribute {
    public string desc;
    public string str;
    public double val;
    public Neutron3529.Enable enable=Neutron3529.Enable.gt;
    public Type ty=typeof(void);
    public Desc(string desc,string str) {
        this.desc = desc;
        this.str = str;
        this.val = 0;
    }
    public Desc(string desc,double val) {
        this.desc = desc;
        this.str = "";
        this.val = val;
    }
    public Desc(string desc) {
        this.desc = desc;
        this.str = "";
        this.val = 0;
    }
    public Desc(string desc,Enable enable,double val) {
        this.desc = desc;
        this.str = "";
        this.val = val;
        this.enable = enable;
    }
    public Desc(string desc,string enable,double val) {
        this.desc = desc;
        this.str = "";
        this.val = val;
        this.enable=enable.ToLower() switch {
            "lt"=>Neutron3529.Enable.lt,
            "le"=>Neutron3529.Enable.le,
            "eq"=>Neutron3529.Enable.eq,
            "ge"=>Neutron3529.Enable.ge,
            "gt"=>Neutron3529.Enable.gt,
            "always"=>Neutron3529.Enable.always,
            "never"=>Neutron3529.Enable.never,
            _=>throw new Exception($"Desc项目禁用选项{enable}无效")
        };
    }
    public string desc_with_enable=>Type.GetTypeCode(this.ty) switch{
        TypeCode.Object=>this.desc,
        TypeCode.Boolean=>this.desc+"（为true时启用此修改）",
        TypeCode.String=>this.desc+$"（字符串长度在除去多余空白字符后{this.enable_op}）",
        _=>this.desc+$"（{this.enable_op}）"
    };
    public string enable_op=>this.enable switch{
        Neutron3529.Enable.lt=>$"小于{this.val}时启用此修改",
        Neutron3529.Enable.le=>$"小于等于{this.val}时启用此修改",
        Neutron3529.Enable.eq=>$"不等于{this.val}时启用此修改",
        Neutron3529.Enable.ge=>$"大于等于{this.val}时启用此修改",
        Neutron3529.Enable.gt=>$"大于{this.val}时启用此修改",
        Neutron3529.Enable.always=>"总是启用此修改",
        Neutron3529.Enable.never=>"不影响当前项目的启用状态",
        _=>throw new Exception($"枚举错误，this.disable的值{this.enable}不正确")
    };
    public string desc_with_disable=>Type.GetTypeCode(this.ty) switch{
        TypeCode.Object=>this.desc,
        TypeCode.Boolean=>this.desc+"（为false时禁用此修改）",
        TypeCode.String=>this.desc+$"（字符串长度在除去多余空白字符后{this.disable_op}）",
        _=>this.desc+$"（{this.disable_op}）"
    };
    public string disable_op=>this.enable switch{
        Neutron3529.Enable.lt=>$"大于等于{this.val}时不主动启用此修改",
        Neutron3529.Enable.le=>$"大于{this.val}时不主动启用此修改",
        Neutron3529.Enable.eq=>$"等于{this.val}时不主动启用此修改",
        Neutron3529.Enable.ge=>$"小于{this.val}时不主动启用此修改",
        Neutron3529.Enable.gt=>$"小于等于{this.val}时不主动启用此修改",
        Neutron3529.Enable.always=>"总是启用此修改",
        Neutron3529.Enable.never=>"不影响当前项目的启用状态",
        _=>throw new Exception($"枚举错误，this.disable的值{this.enable}不正确")
    };
    public bool Enable(ModEntry.Base cls)=>this.val>=0; // for class desc only.
    public bool never<T>(T a, double b)=>false; //
    public bool always<T>(T a, double b)=>true;
    public bool match<T>(T v)=>this.enable switch {
        Neutron3529.Enable.lt=>System.Convert.ToDouble(v) < this.val,
        Neutron3529.Enable.le=>System.Convert.ToDouble(v) <=this.val,
        Neutron3529.Enable.eq=>System.Convert.ToDouble(v) ==this.val,
        Neutron3529.Enable.ge=>System.Convert.ToDouble(v) >=this.val,
        Neutron3529.Enable.gt=>System.Convert.ToDouble(v) > this.val,
        Neutron3529.Enable.always=>true,
        Neutron3529.Enable.never=>false,
        _=>false
    };
    public bool match(string v)=>this.match(v.Length);
}
public abstract class ModEntry : BasePlugin {
    public static HarmonyLib.Harmony harmony;
    public static BepInEx.Configuration.ConfigFile config;
    #if DEBUG
        public static Action<string> logger;
    #else
        public static void logger(string s){}
    #endif
    #if VERBOSE
        public static Action<string> vlogger;
    #else
        public static void vlogger(string s){}
    #endif
    public static Action<string> logwarn;
    public static void logexception(Exception e){
        logwarn(_strexception(e,0));
    }
    static string _strexception(Exception e, int depth){
        var padding=depth==0?"\n":"\n"+new string(' ',depth*4);
        return ("\n"+e.GetType().Name+": "+e.Message+"\n"+e.StackTrace).Replace("\n",padding)+(e.InnerException==null?"":_strexception(e.InnerException,depth+1));
    }
    public abstract class Entry : Base {}
    // Base类不会被Awake自动初始化，而Entry会。
    // Base应该配合new Base().Init()使用。
    public abstract class Base {
        public bool enable=false;
        public string full_desc="";
        Dictionary<FieldInfo, object> dict=new Dictionary<FieldInfo, object>();
        public virtual bool assign(FieldInfo f, Desc desc){ // return whether the type is enabled.
            desc.ty=f.FieldType;
            string full_entry = this.GetType().Name+"."+f.Name;
            switch(Type.GetTypeCode(f.FieldType)) {
                case TypeCode.SByte:
                    dict[f]=config.Bind("config",full_entry,(sbyte)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<sbyte>)dict[f]).Value));
                    return desc.match(((ConfigEntry<sbyte>)dict[f]).Value);
                case TypeCode.Byte:
                    dict[f]=config.Bind("config",full_entry,(byte)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<byte>)dict[f]).Value));
                    return desc.match(((ConfigEntry<byte>)dict[f]).Value);
                case TypeCode.Int16:
                    dict[f]=config.Bind("config",full_entry,(short)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<short>)dict[f]).Value));
                    return desc.match(((ConfigEntry<short>)dict[f]).Value);
                case TypeCode.UInt16:
                    dict[f]=config.Bind("config",full_entry,(ushort)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<ushort>)dict[f]).Value));
                    return desc.match(((ConfigEntry<ushort>)dict[f]).Value);
                case TypeCode.Int32:
                    dict[f]=config.Bind("config",full_entry,(int)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<int>)dict[f]).Value));
                    return desc.match(((ConfigEntry<int>)dict[f]).Value);
                case TypeCode.UInt32:
                    dict[f]=config.Bind("config",full_entry,(uint)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<uint>)dict[f]).Value));
                    return desc.match(((ConfigEntry<uint>)dict[f]).Value);
                case TypeCode.Single:
                    dict[f]=config.Bind("config",full_entry,(float)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<float>)dict[f]).Value));
                    return desc.match(((ConfigEntry<float>)dict[f]).Value);
                case TypeCode.Double:
                    dict[f]=config.Bind("config",full_entry,(double)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<double>)dict[f]).Value));
                    return desc.match(((ConfigEntry<double>)dict[f]).Value);
                case TypeCode.Boolean:
                    dict[f]=config.Bind("config",full_entry,(bool)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<bool>)dict[f]).Value));
                    return ((ConfigEntry<bool>)dict[f]).Value;
                case TypeCode.String:
                    dict[f]=config.Bind("config",full_entry,(string)(f.GetValue(this)),desc.desc_with_enable);
                    f.SetValue(this,(((ConfigEntry<string>)dict[f]).Value));
                    return desc.match(((ConfigEntry<string>)dict[f]).Value);
                default:logwarn($"Unsupport type: {f.Name} : {f.FieldType}");return true;
            }
        }
        public virtual void Init()=>Init(true);
        public virtual void Init(bool check_enable){
#if VERBOSE
            vlogger("正在修改："+this.GetType().Name);
#endif
            var type = this.GetType();
            this.full_desc="";
            Desc dsc = (Desc) Attribute.GetCustomAttribute(type, typeof(Desc));  // 判断类是否有Desc
            if(dsc!=null){
                this.enable=dsc.Enable(this);
                this.assign(type.GetField("enable"),dsc);
                full_desc=dsc.desc;
            }
            foreach(var f in type.GetFields((BindingFlags)(-1))){
                Desc desc = (Desc) Attribute.GetCustomAttribute(f, typeof(Desc));
#if VERBOSE
                vlogger("正在检查："+this.GetType().Name+"的"+f.Name+"字段");
#endif
                if(desc!=null){
#if VERBOSE
                    vlogger("正在检查："+this.GetType().Name+"的"+f.Name+"字段--包含desc信息");
#endif
                    var flag=this.assign(f,desc);
                    enable|=flag;
                    if(full_desc.Length>0){
                        full_desc=full_desc+(dsc==null?" & ":" ： ")+(flag?desc.desc_with_disable:desc.desc_with_enable);
                        dsc=null;
                    }else{
                        full_desc=flag?desc.desc_with_disable:desc.desc_with_enable;
                    }
                }
            }
            if(check_enable){
                if(enable){
                    this.Enable();
                    logger($"已启用 {full_desc}");
                } else {
                    logger($"未启用 {full_desc}");
                }
            } else {
                logger($"开始手工处理 {full_desc} 的数据");
            }
        }
        public virtual void Enable(){
            harmony.PatchAll(this.GetType());
        }
    }

    public abstract class Const:Entry {
        public override bool assign(FieldInfo f, Desc desc){ // return whether the type is enabled.
            base.assign(f,desc);
            return true;
        }
        public override void Init(){
#if VERBOSE
            vlogger("正在修改："+this.GetType().Name);
#endif
            var type = this.GetType();
            this.full_desc="";
            Desc dsc = (Desc) Attribute.GetCustomAttribute(type, typeof(Desc));
            if(dsc!=null){
                full_desc=dsc.desc;
            }
            foreach(var f in type.GetFields((BindingFlags)(-1))){
                Desc desc = (Desc) Attribute.GetCustomAttribute(f, typeof(Desc));
#if VERBOSE
                vlogger("正在检查："+this.GetType().Name+"的"+f.Name+"字段");
#endif
                if(desc!=null){
#if VERBOSE
                    vlogger("正在检查："+this.GetType().Name+"的"+f.Name+"字段--包含desc信息");
#endif
                    if(full_desc.Length>0){
                        full_desc=full_desc+(dsc==null?" & ":" ： ")+desc.desc;
                        dsc=null;
                    }else{
                        full_desc=desc.desc;
                    }
                }
            }
            logger($"读取常数 {full_desc}");
        }
        public override void Enable(){}
    }
    static bool need_init=true;
    public override void Load()=>Awake();
    public virtual void Awake() {
        if(need_init){
            logger("开始注入");
            Type[] t=new Type[0];
            foreach(var type in typeof(ModEntry).Module.GetTypes()){
                if(!type.IsAbstract){
                    try{
#if VERBOSE
                        vlogger("搜索到："+type.ToString());
#endif
                        if(type.IsSubclassOf(typeof(Entry))){
                            Base entry=(Base)(type.GetConstructor(t).Invoke(t));
                            if (entry!=null){
                                entry.Init();
                                // type.GetMethod("Init",t).Invoke(,t);
                            }else{logwarn("Entry："+type+"的type.GetConstructor().Invoke()是null，这多半是mod出了问题，如果你看到这个，请联系mod作者。");}
                        }
                    } catch (Exception e) {
                        logwarn($"{this.plugin_id}({type})出现了意料之外的错误，或许你可以联系Mod作者修复。");
                        logexception(e);
                    }
                }
            }
            need_init=false;
        }
    }
    string plugin_id;
    public ModEntry(string plugin_id){
        this.plugin_id=plugin_id;
        logwarn=Log.LogWarning;
        Log.LogInfo("此Mod使用AGPL-v3许可发布，如果你使用了这里的代码，请按照相同许可发布你修改后的mod。");
        harmony=new Harmony(plugin_id);   // `harmony` is defined in utils.cs
        config=Config;                          // `utils`   is defined in utils.cs
#if DEBUG
        logger=Log.LogInfo;                  // `logger`  is defined in utils.cs
#if VERBOSE
        vlogger=Log.LogWarning;              // `vlogger` is defined in utils.cs
#endif
#endif
    }
}
